﻿namespace LogManager.Core.Models
{
    using LogManager.TestUtility;
    using LogManager.Utils.Authentification;
    using NUnit.Framework;
    using System;
    using System.IO;
    using System.IO.Compression;
    using System.Linq;
    using System.Security.AccessControl;
    using System.Security.Principal;
    using System.Xml.Linq;
    using LogManager.Core.Factories;

    public class TestLogFile
    {
        public const string WORKING_DIR = "TestDir";

        const string userArchiveLogin = "testUserArchive";
        const string userArchivePsw = "testUserArchivePsw";

        const string userSrcLogin = "testUserSource";
        const string userSrcPsw = "testUserSourcePsw";

        const string archiveShareName = "archiveShare";
        const string sourceShareName = "testLogsShare";

        const string CONFIGURATION = @"<?xml version='1.0' encoding='utf-8'?>
                    <Configuration xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:noNamespaceSchemaLocation='schema.xsd'>

                      <Settings>
                        <SmtpSettings EnableNotifications='true'>
                          <From Address='' DisplayName=''/>
                          <To Addresses='' />
                          <SmtpServer Host=''  Port='25' SmtpLogin='' SmtpPass='' EnableSsl='true' />
                        </SmtpSettings>
                      </Settings>

                      <Warehouse Name='Archive2' Ip='{machineName}' Domain='.' Login='" + userArchiveLogin + @"' Password='" + userArchivePsw + @"'>
                        <Plateform Name='Cadrila' Depository='" + archiveShareName + "'  Domain='.' Login='" + userSrcLogin + "' Password='" + userSrcPsw + @"'>
                          <Server Name='DEV-BES01005' Ip='{machineName}' RetentionHours='48'>
                            <Location LogType='File' Name='IIS'>" + sourceShareName + @"</Location>
                          </Server>
                        </Plateform>
                      </Warehouse>
  
                    </Configuration>";

        private XDocument Doc { get; set; }
        private IConfiguration Configuration { get; set; }
        private ILocation Location { get; set; }
        private IServer Server { get; set; }
        private IPlateform Plateform { get; set; }
        private IWarehouse Warehouse { get; set; }

        [TestFixtureSetUp]
        public void InitClass()
        {
            ParseConfiguration();

            CleanUpUsers();
            UserManagement.CreateLocalUserAccount(userArchiveLogin, userArchivePsw);
            UserManagement.CreateLocalUserAccount(userSrcLogin, userSrcPsw);
        }

        [TestFixtureTearDown]
        public void CleanUpClass()
        {
            CleanUpUsers();
        }

        [SetUp]
        public void InitTest()
        {
            CleanUpDirectories();
            Directory.CreateDirectory(WORKING_DIR);
        }

        [TearDown]
        public void CleanUpTest()
        {
            CleanUpDirectories();
        }

        private void ParseConfiguration()
        {
            var conf = CONFIGURATION.Replace("{machineName}", Environment.MachineName);
            this.Doc = XDocument.Parse(conf);
            this.Configuration = new DefaultXmlElementFactory().CreateConfiguration(this.Doc);
            this.Warehouse = this.Configuration.Warehouses.First();
            this.Plateform = this.Warehouse.Plateforms.First();
            this.Server = this.Plateform.Servers.First();
            this.Location = this.Server.Locations.First();
        }

        private static void CleanUpUsers()
        {
            UserManagement.DeleteIfExists(userArchiveLogin);
            UserManagement.DeleteIfExists(userSrcLogin);
        }

        private static void CleanUpDirectories()
        {
            var archiveShare = WindowsShare.GetShareByName(archiveShareName);
            if (archiveShare != null)
            {
                archiveShare.Delete();
            }

            var sourceShare = WindowsShare.GetShareByName(sourceShareName);
            if (sourceShare != null)
            {
                sourceShare.Delete();
            }

            if (Directory.Exists(WORKING_DIR))
                Directory.Delete(WORKING_DIR, true);
        }

        [Test]
        public void LogFile_Archive_OneFile_ExitsAndSizeAndNameOK()
        {
            var testField = this.PrepareTestField();

            var logFileName = "logfile10.txt";
            var logFileSize = 3 * 1024;

            // create logs
            var logFileInfo = FileUtility.CreateFile(Path.Combine(testField.SourceDir.FullName, logFileName), DateTime.Now.AddDays(-10), logFileSize);
            var logFileBytes = FileUtility.ReadFully(logFileInfo.Open(FileMode.Open));
            var logFile = LogFile.GetFiles(this.Location).First();
            logFile.Archive();

            var assumedCreatedArchive = new FileInfo(Path.Combine(this.Location.FullPathDestination, logFileName + ".zip"));
            Assert.AreEqual(assumedCreatedArchive.FullName, logFile.ArchivedFile.FullName);

            Assert.IsFalse(logFileInfo.Exists, "source log file should be deleted");

            using (ZipArchive zip = ZipFile.OpenRead(testField.ArchiveLocationDir.FullName + "\\" + logFileName + ".zip"))
            {
                Assert.AreEqual(1, zip.Entries.Count);

                var file = zip.Entries.First();
                Assert.AreEqual(logFileName, file.Name);
                Assert.AreEqual(FileUtility.ReadFully(file.Open()), logFileBytes);
            }
        }

        [Test]
        public void LogFile_Archive_OneFile_ProgressResultOK()
        {
            var testField = this.PrepareTestField();

            var logFileName = "logfile10.txt";
            var logFileSize = 3 * 1024;

            // create logs
            var logFileInfo = FileUtility.CreateFile(Path.Combine(testField.SourceDir.FullName, logFileName), DateTime.Now.AddDays(-10), logFileSize);
            var logFileBytes = FileUtility.ReadFully(logFileInfo.Open(FileMode.Open));
            var progress = new Progress<LogFileArchivingProgress>();
            var logFile = LogFile.GetFiles(this.Location).First();

            progress.ProgressChanged += (object sender, LogFileArchivingProgress log) =>
            {
                var logFileLocal = log.LogFile;
                switch (logFileLocal.LogFileStep)
                {
                    case LogFileArchivingStep.InSource:
                        Assert.IsTrue(logFileLocal.SourceFile.Exists);
                        Assert.IsTrue(logFileLocal.ArchivedFile == null);
                        break;
                    case LogFileArchivingStep.Archived:
                        Assert.IsTrue(logFileLocal.SourceFile.Exists);
                        Assert.IsTrue(logFileLocal.ArchivedFile.Exists);
                        break;
                    case LogFileArchivingStep.SourceCleared:
                        Assert.IsFalse(logFileLocal.SourceFile.Exists);
                        Assert.IsTrue(logFileLocal.ArchivedFile.Exists);
                        break;
                    default:
                        break;
                }
            };

            logFile.Archive(progress);
        }

        [Test]
        public void LogFile_Archive_fileInSubDirectory_ExitsAndSizeAndNameOK()
        {
            var testField = this.PrepareTestField();

            var logFileName = "logfile10.txt";
            var subDir = "subdir";
            var logFileRelativeName = Path.Combine(subDir, logFileName);
            var logFileSize = 3 * 1024;

            // create logs
            var subDirInfo = Directory.CreateDirectory(Path.Combine(testField.SourceDir.FullName, subDir));
            var logFileInfo = FileUtility.CreateFile(Path.Combine(testField.SourceDir.FullName, logFileRelativeName), DateTime.Now.AddDays(-10), logFileSize);
            var logFileBytes = FileUtility.ReadFully(logFileInfo.Open(FileMode.Open));
            var logFile = LogFile.GetFiles(this.Location).First();
            logFile.Archive();

            var assumedCreatedArchive = new FileInfo(Path.Combine(this.Location.FullPathDestination, logFileRelativeName + ".zip"));
            Assert.AreEqual(assumedCreatedArchive.FullName, logFile.ArchivedFile.FullName);
            Assert.IsFalse(logFileInfo.Exists, "source log file should be deleted");
            Assert.IsTrue(subDirInfo.Exists, "Sub directories should not be removed ");

            using (ZipArchive zip = ZipFile.OpenRead(testField.ArchiveLocationDir.FullName + "\\" + logFileRelativeName + ".zip"))
            {
                Assert.AreEqual(1, zip.Entries.Count);

                var file = zip.Entries.First();
                Assert.AreEqual(logFileName, file.Name);
                Assert.AreEqual(FileUtility.ReadFully(file.Open()), logFileBytes);
            }

            Assert.Throws<InvalidOperationException>(() => logFile.Archive(), " a file Cannot be archived multiple times");
        }

        private void ExecuteAsUserarchive(Action action)
        {
            using (var imp = new Impersonator("testUserArchive", null, "testUserArchivePsw"))
            {
                action();
            }
        }

        private void ExecuteAsUsersource(Action action)
        {
            using (var imp = new Impersonator("testUserSource", null, "testUserSourcePsw"))
            {
                action();
            }
        }

        private TestFieldResult PrepareTestField()
        {
            // create archive
            var archiveDir = Directory.CreateDirectory(Path.Combine(WORKING_DIR, archiveShareName));
            var archiveLocationDir = Directory.CreateDirectory(Path.Combine(WORKING_DIR, archiveShareName, this.Plateform.Name, this.Server.Name, this.Location.Name));
            NTAccount accountArchiv = new NTAccount(Environment.MachineName, userArchiveLogin);
            RightsManager.SetAccess(archiveLocationDir.FullName, accountArchiv, AccessControlType.Allow);

            // create source
            var sourceDir = Directory.CreateDirectory(Path.Combine(WORKING_DIR, "sourceDir"));
            NTAccount accountSrc = new NTAccount(Environment.MachineName, userSrcLogin);
            RightsManager.SetAccess(sourceDir.FullName, accountSrc, AccessControlType.Allow);

            // create shares
            var resShareArch = WindowsShare.Create(archiveDir.FullName, archiveShareName, WindowsShare.ShareType.DiskDrive, null, null, null);
            var resShareSrc = WindowsShare.Create(sourceDir.FullName, sourceShareName, WindowsShare.ShareType.DiskDrive, null, null, null);
            var archiveShare = WindowsShare.GetShareByName(archiveShareName);
            var res = archiveShare.SetPermission(Environment.MachineName, userArchiveLogin, WindowsShare.AccessMaskTypes.FullControl);
            var sourceShare = WindowsShare.GetShareByName(sourceShareName);
            var res2 = sourceShare.SetPermission(Environment.MachineName, userSrcLogin, WindowsShare.AccessMaskTypes.FullControl);

            return new TestFieldResult()
            {
                ArchiveDir = archiveDir,
                ArchiveLocationDir = archiveLocationDir,
                SourceDir = sourceDir
            };
        }

        private class TestFieldResult
        {
            public DirectoryInfo SourceDir { get; set; }
            public DirectoryInfo ArchiveDir { get; set; }
            public DirectoryInfo ArchiveLocationDir { get; set; }
        }
    }
}
